//
//	HOME AUTOMATION GATEWAY PROJECT
//
//  (c) 2008 mocom software GmbH & Co KG
//	for European Microsoft Innovation Center
//
//  $Author: volker $
//  $Date: 2009-02-26 16:55:56 +0100 (Do, 26. Feb 2009) $
//  $Revision: 276 $
//
//  Microsoft dotNetMF Project
//  Copyright ©2001,2002,2003,2004 Microsoft Corporation
//  One Microsoft Way, Redmond, Washington 98052-6399 U.S.A.
//  All rights reserved.
//  MICROSOFT CONFIDENTIAL
//
//-----------------------------------------------------------------------------
#include <tinyhal.h>
#include "..\stnand0x.h"
#include "nand_driver.h"

extern void msac_dbg_dump(char *msg, void *buffer, unsigned len, unsigned size);

#undef DEBUG
#define DEBUG 0

#if DEBUG
#define DPRINTF	debug_printf
#define DERROR		debug_printf
#define DDUMP		msac_dbg_dump
#define DBGVALIDATEPTR(x)	validate_ptr((UINT32)x,#x)
#else
#define DPRINTF	null_printf
#define DERROR		debug_printf
#define DDUMP
#define DBGVALIDATEPTR(x)
extern void null_printf(char *fmt, ...);
#endif

// CURRENTLY WE SUPPORT 8-BIT IF AND BIG SECTOR ONLY!
#ifndef AT91C_SMARTMEDIA_BASE
#define AT91C_SMARTMEDIA_BASE			0x40000000
#endif

#ifndef AT91_SMART_MEDIA_CLE
#define AT91_SMART_MEDIA_CLE			(AT91C_SMARTMEDIA_BASE | (1 << 21))
#endif

#ifndef AT91_SMART_MEDIA_ALE
#define AT91_SMART_MEDIA_ALE			(AT91C_SMARTMEDIA_BASE | (1 << 22))
#endif

// timeout
#define HAG_WAIT_INFINIT				((unsigned)-1)
#define HAG_NAND_STD_TIMEOUT			HAG_WAIT_INFINIT

// GPIO bits to control NAND operation
#define HAG_NAND_OE						AT91_GPIO_Driver::PC0
#define HAG_NAND_WE						AT91_GPIO_Driver::PC1
#define HAG_NAND_RB						AT91_GPIO_Driver::PC3
#define HAG_NAND_CE						AT91_GPIO_Driver::PB2

/// Nand flash commands
#define COMMAND_READ_1					0x00
#define COMMAND_READ_2					0x30
#define COMMAND_RANDOM_OUT				0x05
#define COMMAND_RANDOM_OUT_2			0xE0
#define COMMAND_RANDOM_IN				0x85
#define COMMAND_READID					0x90
#define COMMAND_WRITE_1					0x80
#define COMMAND_WRITE_2					0x10
#define COMMAND_ERASE_1					0x60
#define COMMAND_ERASE_2					0xD0
#define COMMAND_STATUS					0x70
#define COMMAND_RESET					0xFF

//--//

//--// speedy inline function for low level access

#if DEBUG
void validate_ptr(UINT32 ptr, char *msg)
{
	if (ptr & 3)
	debug_printf("UNALIGNED POINTER %s=0x%8.8lx in line%u\r\n",msg,ptr,__LINE__);
	if (ptr > 0x21ffffff)
	debug_printf("POINTER OUT OF MEMORY %s=0x%8.8lx in line%u\r\n",msg,ptr,__LINE__);
}
#endif

// read a data byte
static inline UINT8 hag_nand_read_byte()
{
	return  (*((volatile UINT8 *)(AT91C_SMARTMEDIA_BASE)));
}

// write data byte
static inline void hag_nand_write_byte(UINT8 data)
{
	*((volatile UINT8 *)(AT91C_SMARTMEDIA_BASE)) = data;
}

// write a command
static inline void hag_nand_write_command(UINT8 command)
{
	*((volatile UINT8 *)(AT91_SMART_MEDIA_CLE)) = command;
}

// write random address byte
static inline void hag_nand_set_sector_address(UINT32 sector)
{
	*((volatile UINT8 *)(AT91_SMART_MEDIA_ALE)) = (UINT8)((sector >>  0) & 0xFF);
	*((volatile UINT8 *)(AT91_SMART_MEDIA_ALE)) = (UINT8)((sector >>  8) & 0xFF);
	*((volatile UINT8 *)(AT91_SMART_MEDIA_ALE)) = (UINT8)((sector >> 16) & 0xFF);
}

static inline void hag_nand_set_random_address(unsigned offset)
{
	*((volatile UINT8 *)(AT91_SMART_MEDIA_ALE)) = (UINT8)((offset >>  0) & 0xFF);
	*((volatile UINT8 *)(AT91_SMART_MEDIA_ALE)) = (UINT8)((offset >>  8) & 0x0F);
}

static inline void hag_nand_set_address(UINT32 sector, unsigned offset)
{
	hag_nand_set_random_address(offset);
	hag_nand_set_sector_address(sector);
}

static inline unsigned hag_nand_get_status()
{
	hag_nand_write_command(COMMAND_STATUS);
	return hag_nand_read_byte();
}

// wait for NAND REady (via GPIO). Important for timed
// operations like write and erase
static inline unsigned hag_nand_wait_ready(UINT32 usec)
{
	UINT32 waited = 0;

	// not yet
	usec = 0;

	while (!CPU_GPIO_GetPinState(HAG_NAND_RB))
	waited++;

	return TRUE;
}

// enable CE
static inline void hag_nand_enable_ce()
{
	//CPU_GPIO_SetPinState(HAG_NAND_CE, FALSE);
}

// disable CE
static inline void hag_nand_disable_ce()
{
	//CPU_GPIO_SetPinState(HAG_NAND_CE, TRUE);
}

// this function is used at end of erase, read and write
// to wait for ok and read back status
static inline unsigned hag_nand_get_status_and_disable(UINT32 usec, unsigned rlen)
{
	unsigned status;

	hag_nand_wait_ready(usec);

	status = hag_nand_get_status();
	if (status & STATUS_ERROR || (status & STATUS_READY) == 0) {
		DPRINTF( "NIF: hag_nand_get_status_and_disable status=%x\r\n",status);
		rlen = 0;
	}

	hag_nand_disable_ce();

	return rlen;
}

// init hardware
int HALNandIfControllerInitialize()
{
	CPU_GPIO_DisablePin(HAG_NAND_OE, RESISTOR_DISABLED, 0, GPIO_ALT_MODE_1);
	CPU_GPIO_DisablePin(HAG_NAND_WE, RESISTOR_DISABLED, 0, GPIO_ALT_MODE_1);
	CPU_GPIO_EnableInputPin(HAG_NAND_RB, TRUE, NULL, GPIO_INT_NONE, RESISTOR_PULLUP);
	CPU_GPIO_EnableOutputPin(HAG_NAND_CE, TRUE);

	hag_nand_enable_ce();
	hag_nand_write_command(COMMAND_RESET);
	hag_nand_wait_ready(HAG_NAND_STD_TIMEOUT);
	hag_nand_disable_ce();

	return TRUE;
}


void HALNandIfControllerReadId(unsigned *ManufacturerCode, unsigned *DeviceCode)
{
	hag_nand_enable_ce();

	hag_nand_write_command(COMMAND_READID);
	hag_nand_set_address(0,0);

	*ManufacturerCode  = hag_nand_read_byte();
	*DeviceCode = hag_nand_read_byte();

	hag_nand_disable_ce();
}

unsigned HALNandIfControllerEraseBlock(UINT32 Sector, UINT32 usec)
{
	unsigned rval;

	DPRINTF( "NIF: Era(%lu,%u)\r\n",Sector,usec);

	hag_nand_enable_ce();

	if (hag_nand_get_status() & STATUS_NO_WP) {
		hag_nand_write_command(COMMAND_ERASE_1);
		hag_nand_set_sector_address(Sector);
		hag_nand_write_command(COMMAND_ERASE_2);
		// hag_nand_disable_ce(); called in hag_nand_get_status_and_disable
		rval =  hag_nand_get_status_and_disable(usec,FLASH_SECTOR_SIZE);
	} else {
		rval = 0;
		hag_nand_disable_ce();
	}


	if (rval == 0)
	DERROR( "NIF: Era - FAIL in %u\r\n",__LINE__);

	return rval;
}

static void hag_nand_write_data(UINT8 *pSectorData, unsigned tobewritten)
{
	unsigned blks    = ((unsigned)tobewritten) >> 4;
	unsigned residue = ((unsigned)tobewritten) & 0xf;
	
	// write blocks loop
	while (blks--)
	{
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
		hag_nand_write_byte(*pSectorData++);
	}
	// write residue loop
	while (residue--)
	{
		hag_nand_write_byte(*pSectorData++);
	}
}

static void hag_nand_fill_data(UINT8 *pSectorData, unsigned tobewritten)
{
	unsigned blks    = ((unsigned)tobewritten) >> 4;
	unsigned residue = ((unsigned)tobewritten) & 0xf;
	
	// write blocks loop
	while (blks--)
	{
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
		hag_nand_write_byte(*pSectorData);
	}
	// write residue loop
	while (residue--)
	{
		hag_nand_write_byte(*pSectorData);
	}
}

static void hag_nand_read_data(UINT8 *pSectorData, unsigned toberead)
{
	unsigned blks    = ((unsigned)toberead) >> 4;
	unsigned residue = ((unsigned)toberead) & 0xf;
	
	// Read blks loop --- if toderead is zero, blks & residue are too!
	while (blks--)
	{
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
		*pSectorData++ = hag_nand_read_byte();
	}
	// write residue loop
	while (residue--)
	{
		*pSectorData++ = hag_nand_read_byte();
	}
}


int HALNandIfControllerIsErased(UINT32 Sector)
{
	int 	erased = -1;
	UINT32	burst[FLASH_SECTOR_SIZE/64];	// must be power of two
	
	hag_nand_enable_ce();
	hag_nand_write_command(COMMAND_READ_1);
	hag_nand_set_address(Sector,0);
	hag_nand_write_command(COMMAND_READ_2);
	hag_nand_wait_ready(HAG_NAND_STD_TIMEOUT);
	
	for (unsigned i=0;i<FLASH_SECTOR_SIZE && erased != 0;i+=sizeof(burst)) 
	{
		hag_nand_read_data((UINT8*)burst,sizeof(burst));
		for (unsigned j=0;j<ARRAYSIZE(burst);j++) 
		{
			if (burst[j] != 0xffffffff) 
			{
				erased = 0;
				break;
			}
		}
	}
	hag_nand_disable_ce();
	
	return erased;
}

unsigned HALNandIfControllerReadSector(UINT32 StartSector,
unsigned offset,
UINT8 *pSectorData,
unsigned Read)
{
	unsigned finalidx;
	unsigned toberead;
	unsigned rval;
#if DEBUG
	UINT8 *pSectorDataDbg = pSectorData;
#endif

	//DBGVALIDATEPTR(pSectorData);
	//DBGVALIDATEPTR(pSpare);

	//DPRINTF( "NIF: Rd(%lu,%u,0x%8.8lx,%u,0x%8.8lx,%u)\r\n",StartSector, offset, pSectorData, Read, pSpare, SpareSize);

	// how many bytes left when we substarct offset
	toberead = FLASH_SECTOR_SIZE - (offset & (FLASH_SECTOR_SIZE-1));

	// if we have fewer bytes to read that left in sector after
	// substracting offset, we must limit the operation
	if (toberead > Read) {
		toberead = Read;
	}

	// if we don't need to read data or spare, we are wrong
	if (toberead == 0) {
		DERROR( "NIF: Rd FAIL in %u\r\n",__LINE__);
		return 0;
	}

	if (toberead && pSectorData == NULL) {
		DERROR( "NIF: Rd FAIL in %u\r\n",__LINE__);
		return 0;
	}

	// clac blks/residue for optimization
	// this will be our index after reading data
	finalidx = offset + toberead;

	//DPRINTF( "NIF: blks=%u residue=%u finalidx=%u Read=%u toberead=%u FLASH_SECTOR_SIZE=%u\r\n",
	//		blks,residue,finalidx,Read,toberead,FLASH_SECTOR_SIZE);

	// issue cmd.
	hag_nand_enable_ce();
	hag_nand_write_command(COMMAND_READ_1);
	hag_nand_set_address(StartSector,offset);
	hag_nand_write_command(COMMAND_READ_2);
	hag_nand_wait_ready(HAG_NAND_STD_TIMEOUT);
	hag_nand_read_data(pSectorData,toberead);
	
	rval = hag_nand_get_status_and_disable(0,toberead);

	if (rval == 0)
	DERROR( "NIF: Rd in line %u Read=0x%x\r\n",__LINE__,rval);
	return rval;
}


unsigned HALNandIfControllerWriteSector(UINT32 StartSector,
unsigned offset,
UINT8 *pSectorData,
unsigned Write,
UINT32 usec,
int fill)
{
	unsigned tobewritten;
	unsigned rval;
#if DEBUG
	UINT8 *pSectorDataDbg = pSectorData;
#endif

	//DPRINTF( "NIF: Wr(%lu,%u,0x%8.8lx,%u,0x%8.8lx,%u)\r\n",StartSector, offset, pSectorData, Write, pSpare, SpareSize);

	// how many bytes left when we substract offset
	tobewritten = FLASH_SECTOR_SIZE - (offset & (FLASH_SECTOR_SIZE-1));

	// if we have fewer bytes to read than left in sector after
	// substracting offset, we must limit the operation
	if (tobewritten > Write) {
		tobewritten = Write;
	}

	// if we don't need to read data, we are wrong
	if (tobewritten == 0) {
		DERROR( "NIF: Wr FAIL in %u\r\n",__LINE__);
		return 0;
	}

	// null pointer check	
	if ( pSectorData == NULL) {
		DERROR( "NIF: Wr FAIL in %u\r\n",__LINE__);
		return 0;
	}

	// enbale interface and check for WP
	hag_nand_enable_ce();
	if (!(hag_nand_get_status() & STATUS_NO_WP)) {
		DERROR( "NIF: Wr FAIL (WP) in %u\r\n",__LINE__);
		hag_nand_disable_ce();
		return 0;
	}

	// issue command and address, read data
	hag_nand_write_command(COMMAND_WRITE_1);
	hag_nand_set_address(StartSector,offset);
	if (fill) 
	hag_nand_fill_data(pSectorData,tobewritten);	
	else
	hag_nand_write_data(pSectorData,tobewritten);	
	hag_nand_write_command(COMMAND_WRITE_2);
	
	rval = hag_nand_get_status_and_disable(usec,tobewritten);

	if (rval == 0)
	DERROR( "NIF: Wr in line %u rval=0x%x\r\n",__LINE__,rval);

	return rval;
}

unsigned HALNandIfControllerReadSpare(UINT32 StartSector,UINT8 *pSpareData,unsigned Read)
{
	unsigned rval;
	
	// if we don't need to read data or spare, we are wrong
	if (Read == 0) {
		DERROR( "NIF: Spare Rd FAIL in %u\r\n",__LINE__);
		return 0;
	}

	if (pSpareData == NULL) {
		DERROR( "NIF: Spare Rd FAIL in %u\r\n",__LINE__);
		return 0;
	}
	
	if (Read > FLASH_SPARE_SIZE) {
		DERROR( "NIF: Spare Rd size missmatch &u in line %u\r\n",Read,__LINE__);		
		Read = FLASH_SPARE_SIZE;		
	}
	
	// issue cmd.
	hag_nand_enable_ce();
	hag_nand_write_command(COMMAND_READ_1);
	hag_nand_set_address(StartSector,FLASH_SECTOR_SIZE);
	hag_nand_write_command(COMMAND_READ_2);
	hag_nand_wait_ready(HAG_NAND_STD_TIMEOUT);
	hag_nand_read_data(pSpareData,Read);
	
	rval = hag_nand_get_status_and_disable(0,Read);

	if (rval == 0)
	DERROR( "NIF: Spare Rd in line %u rval=0x%x\r\n",__LINE__,rval);	

	return rval;	
}

unsigned HALNandIfControllerWriteSpare(UINT32 StartSector,UINT8 *pSpareData,unsigned Write,UINT32 usec)
{
	unsigned rval;
	
	// if we don't need to read data or spare, we are wrong
	if (Write == 0) {
		DERROR( "NIF: Spare Wr FAIL in %u\r\n",__LINE__);
		return 0;
	}

	if (pSpareData == NULL) {
		DERROR( "NIF: Spare Wr FAIL in %u\r\n",__LINE__);
		return 0;
	}
	
	if (Write > FLASH_SPARE_SIZE) {
		DERROR( "NIF: Spare Wr size missmatch &u in line %u\r\n",Write,__LINE__);		
		Write = FLASH_SPARE_SIZE;
	}
	
	hag_nand_enable_ce();
	if (!(hag_nand_get_status() & STATUS_NO_WP)) {
		DERROR( "NIF: Wr FAIL (WP) in %u\r\n",__LINE__);
		hag_nand_disable_ce();
		return 0;
	}
	hag_nand_write_command(COMMAND_WRITE_1);
	hag_nand_set_address(StartSector,FLASH_SECTOR_SIZE);	
	hag_nand_write_data(pSpareData,Write);	
	hag_nand_write_command(COMMAND_WRITE_2);

	rval = hag_nand_get_status_and_disable(usec,Write);

	if (rval == 0)
	DERROR( "NIF: Spare Wr in line %u rval=0x%x\r\n",__LINE__,rval);	
	
	return rval;	
}

